/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package network;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;


import bbqtorrent.Torrent;
import bbqtorrent.TorrentManager;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import tools.Format;
import tools.Utils;

public class Serveur {

	private Socket socket;
	private InputStream input;
	private OutputStream output;
	private boolean closed;   

	private boolean choked;
	private boolean is_choked;
	private boolean interested;
	private boolean is_interested;

	private String peer_id;
	private String software_id;

	private TorrentManager tm;
	private Torrent torrent;

	private static int compteur = 0;
	private int id;
	private int idGroupeServeur;

	private Sender sender;

	public Serveur(int idGroupeServeur, Socket s, TorrentManager tm) throws IOException, NotConformMessageException, InterruptedException {
		this.socket = s;
		this.socket.setSoTimeout(5000);

		this.closed = false;
		this.input = this.socket.getInputStream();
		this.output = this.socket.getOutputStream();

		this.choked = true;
		this.interested = false;
		this.is_choked = true;
		this.is_interested = false;

		this.peer_id = "";
		this.software_id = "";

		this.tm = tm;
		this.torrent = null;

		//System.out.println(this.socket);

		Serveur.compteur++;
		this.id = Serveur.compteur;
		this.idGroupeServeur = idGroupeServeur;
		this.sender = new Sender(output);
		//System.out.println("Serveur{"+this.idGroupeServeur+","+ this.id +"}|construct");

		if(this.getHandShake()) {
			System.out.println("Successful handshake");
		} else {
			System.out.println("Handshake failed");
			this.close();
		}
	}


	public void handleRead(int readReturn) throws IOException {
		if(readReturn == -1) {
			this.close();
		}
	}

	public void close() throws IOException {
		this.input.close();
		this.output.close();
		this.socket.close();
		this.closed = true;
	}

	public boolean getHandShake() throws IOException, InterruptedException {    
		
		int length =0;
		byte[] info_hash = new byte[20];
		byte[] bytes = new byte[1];


		//longueur protocol
	
		this.handleRead(this.input.read(bytes));
		length  = new Integer(bytes[0]).intValue();
		

		System.out.println("length:"+length);
		
		if(length > 0 && length < 50) {
			bytes = new byte[length];
			
			//protocol
			this.handleRead(this.input.read(bytes));
			this.software_id = new String(bytes);
			//System.out.println(this.software_id);
			if(!this.software_id.equals("BitTorrent protocol")) {
				System.out.println("Protocol not supported !");
				return false;				
			}
			//reservé
			bytes = new byte[8];
			this.handleRead(this.input.read(bytes));
			//info hash
			bytes = new byte[20];
			this.handleRead(this.input.read(bytes));
			info_hash = bytes;
			//peer id
			bytes = new byte[20];
			this.handleRead(this.input.read(bytes));
			this.peer_id = new String(bytes);
	
			this.torrent = tm.getTorrent(info_hash);
	
			if(this.torrent != null) {
				this.output.write(19);
				this.output.write("BitTorrent protocol".getBytes());
				bytes = new byte[8];
				this.output.write(bytes);
				this.output.write(this.torrent.getHashInfo());
				this.output.write(Utils.generatePeerId());
				
				this.sender.sendBitfield(1);
				this.sender.sendInterested();	
				
				/*System.out.println(length);
				System.out.println(this.software_id);
				System.out.println(this.peer_id);
				System.out.println(info_hash);*/
		
		
				//Thread.sleep(5000);
				//Sender.sendUnchoke(output);
				// Sender.sendInterested(output);
				//                Sender.sendUnchoke(output);
				//Sender.sendKeepALive(output);	
				
				//this.sender.sendUnchoke();
				//this.sender.sendHave(1);
				
				return true;
			} else {
				System.out.println("Torrent not found");
				this.close();
				
				return false;
			}
		} else {
			System.out.println("Strange received first byte !");
			return false;
		}
	}

	// ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).put(buffer).order(ByteOrder.BIG_ENDIAN).array()
	public void receive() throws IOException, NoSuchAlgorithmException {
		//System.out.println(">>>receive");
		byte[] length = new byte[4];
		byte[] id = new byte[1];

		int length_message;
		int code;		

		//System.out.println("Before read()");

		if(input.available() > 0) {
			this.handleRead(input.read(length, 0, 4));			

			//System.out.println("After read()");

			/*for(int i=0; i<length.length; i++) {
				System.out.println("b>"+length[i]);
			}*/
			length_message = ByteBuffer.allocate(4).put(length).order(ByteOrder.LITTLE_ENDIAN).getInt(0);
			//System.out.println("length:"+length_message);

			if (length_message == 0) {
				//DO A KEEP ALIVE SYSTEM
				//System.out.println("KeepAlive");
				this.socket.setKeepAlive(true);
			} else if (length_message >= 1) {
				this.handleRead(input.read(id, 0, 1));			
				code = ByteBuffer.allocate(4).put(id).order(ByteOrder.LITTLE_ENDIAN).getInt(0);
				//System.out.println("code:"+code);
				/*System.out.println(*/this.handleMessage(code, length_message);/*);*/
			}

		} else {
			//System.out.println("Rien à lire, on passe !");
		}
		//System.out.println("receive<<<");

	}

	private String handleMessage(int code, int length) throws IOException, NoSuchAlgorithmException {
		//System.out.println(">>>handleMessage");
		//String line = "";
		//int index = -1;
		//int begin = -1;
		//int length = -1;
		byte[] buffer = new byte[4];		

		switch (code) {
		case 0:
			System.out.println("setChocked(true)");
			this.setChoked(true);
			break;
		case 1:
			System.out.println("setChocked(false)");
			this.setChoked(false);
			break;
		case 2:
			System.out.println("setInterested(true)");
			this.setInterested(true);
			break;
		case 3:
			System.out.println("setInterested(false)");
			this.setInterested(false);
			break;
		case 4:
			System.out.print("have(); value=");
			Arrays.fill(buffer, (byte) 0);          	
			this.handleRead(this.input.read(buffer, 0, 4));
			int indexHave = ByteBuffer.allocate(4).put(buffer).getInt(0);
			System.out.println(indexHave);
			//this.sender.sendRequest(indexHave, 0, this.torrent.getPieceLength());
			//Fonction have, signifiant que le client a reçu la partie signalée par l'index
			break;
		case 5:
			//bitfield ! DAFUCK ?
			System.out.println("Bitfield");
			byte[] bitField = new byte[length-1];
			this.handleRead(input.read(bitField, 0, (length-1)));

			if(bitField.length != this.torrent.getPiecesString().size()) {
				System.out.println("Wrong bitfield");
				this.close();
			} 
			
			//What's now ?
			break;
		case 6:
			//Code request
			System.out.print("request(); value=");     	           	

			//traiter le request
			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer, 0, 4));
			int indexRequest = ByteBuffer.allocate(4).put(buffer).getInt(0);

			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer, 0, 4));
			int beginRequest = ByteBuffer.allocate(4).put(buffer).getInt(0);

			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer, 0, 4));
			int lengthRequest = ByteBuffer.allocate(4).put(buffer).getInt(0);

			int pieceLength = this.torrent.getPieceLength();
			//int pieceLength = 8;

			System.out.println("index:"+indexRequest+"; begin:"+beginRequest+"; length:"+lengthRequest);

			if(lengthRequest <= pieceLength) {

				// RECUPERER LA PIECE EN FONCTION DE indexRequest
				FileInputStream is = new FileInputStream("/home/etudiants/info/dsegard/bbqtorrent/project/fichier");

				byte[] b = new byte[lengthRequest];
				Arrays.fill(b, (byte) 0);

				is.skip(beginRequest);				

				if(is.available() >= b.length) {
					is.read(b);
				} else {
					b = new byte[is.available()];
					is.read(b);
				}

				//<len=0009+X><id=7><index><begin><block>
				this.output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(9+lengthRequest).order(ByteOrder.BIG_ENDIAN).array());
				this.output.write(7);
				this.output.write(ByteBuffer.allocate(4).putInt(indexRequest).order(ByteOrder.LITTLE_ENDIAN).array());
				this.output.write(ByteBuffer.allocate(4).putInt(beginRequest).order(ByteOrder.LITTLE_ENDIAN).array());
				this.output.write(b);
			}

			break;
		case 7:
			//code Piece
			System.out.println("Piece");   

			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer, 0, 4));
			int indexPiece = ByteBuffer.allocate(4).put(buffer).getInt(0);

			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer, 0, 4));
			int beginPiece = ByteBuffer.allocate(4).put(buffer).getInt(0);


			byte[] block = new byte[length-9];
			this.input.read(buffer);
			MessageDigest cript = MessageDigest.getInstance("SHA-1");
			cript.reset();
			cript.update(block);	

			String hash = Format.byteArray2Hex(cript.digest());

			System.out.println("premier bout : "+hash+"\nsecond  bout : "+this.torrent.getPiecesString().get(indexPiece));
			//                        if (hash.equals(this.torrent.getPiecesString().get(indexPiece))) {

			if (this.torrent.isMulti()){

				System.out.println("Fuuuu");

			} else {
				System.out.println("Ecriture !!!!!!");
				//Ecriture du block dans le fichier
				FileOutputStream lefichier = new FileOutputStream(new File("D:\\Work\\ma_video.avi"));
				lefichier.write(block, indexPiece*this.torrent.getPieceLength(), this.torrent.getPieceLength());
				lefichier.close();
			}
			//                       }

			this.sender.sendHave(indexPiece);

			Arrays.fill(buffer, (byte) 0);
			// READ BLOCK RIGHT HERE;

			System.out.println("index:"+indexPiece+"; begin:"+beginPiece+"; block:");

			//que faire ?
			break;
		case 8:
			//Code Cancel ( annulation d'une demande de pièce )
			System.out.println("Cancel");

			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer,0, 4));
			int indexCancel = ByteBuffer.allocate(4).put(buffer).getInt(0);

			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer,0, 4));
			int beginCancel = ByteBuffer.allocate(4).put(buffer).getInt(0);

			Arrays.fill(buffer, (byte) 0);
			this.handleRead(this.input.read(buffer,0, 4));
			int lengthCancel = ByteBuffer.allocate(4).put(buffer).getInt(0);

			System.out.println("index:"+indexCancel+"; begin:"+beginCancel+"; length:"+lengthCancel);
			break;
		default:
			System.out.println("Type de message '"+code+"' inconnu !");
			this.close();
			break;
		}

		//System.out.println("handleMessage<<<");

		return "";
	}

	public boolean isChoked() {
		return choked;
	}

	public void setChoked(boolean choked) {
		this.choked = choked;
	}

	public boolean isInterested() {
		return interested;
	}

	public void setInterested(boolean interested) {
		this.interested = interested;
	}

	public boolean isIs_choked() {
		return is_choked;
	}

	public void setIs_choked(boolean is_choked) {
		this.is_choked = is_choked;
	}

	public boolean isIs_interested() {
		return is_interested;
	}

	public void setIs_interested(boolean is_interested) {
		this.is_interested = is_interested;
	}

	public String getPeer_id() {
		return peer_id;
	}

	public void setPeer_id(String peer_id) {
		this.peer_id = peer_id;
	}

	public String getSoftware_id() {
		return software_id;
	}

	public void setSoftware_id(String softwareId) {
		software_id = softwareId;
	}

	public Torrent getTorrent() {
		return torrent;
	}

	public void setTorrent(Torrent torrent) {
		this.torrent = torrent;
	}



	public boolean isClosed() {
		return closed;
	}


	@Override
	public String toString() {
		return "Serveur [peer_id=" + peer_id + ", socket=" + socket
		+ ", software_id=" + software_id + "]";
	}


}
